package com.devbobcorn.nekoration.client.rendering.entities;

import com.devbobcorn.nekoration.Nekoration;
import com.mojang.blaze3d.matrix.MatrixStack;
import com.mojang.blaze3d.vertex.IVertexBuilder;

import net.minecraft.client.Minecraft;
import net.minecraft.client.entity.player.AbstractClientPlayerEntity;
import net.minecraft.client.renderer.IRenderTypeBuffer;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.IEntityRenderer;
import net.minecraft.client.renderer.entity.model.BipedModel;
import net.minecraft.client.renderer.entity.model.EntityModel;
import net.minecraft.client.renderer.entity.model.PlayerModel;
import net.minecraft.client.renderer.model.ModelRenderer;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.entity.Entity;
import net.minecraft.entity.LivingEntity;
import net.minecraft.entity.Pose;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.PlayerModelPart;
import net.minecraft.item.CrossbowItem;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.item.UseAction;
import net.minecraft.util.Direction;
import net.minecraft.util.Hand;
import net.minecraft.util.HandSide;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.vector.Vector3d;
import net.minecraft.util.math.vector.Vector3f;
import net.minecraft.util.text.TextFormatting;

public class ElectroRenderer {
    private final PlayerModel<AbstractClientPlayerEntity> model;
    public final ElectroHandRenderer hand;
    public final ElectroElytraRenderer elytra;
    public final boolean slim;

    public ElectroRenderer(IEntityRenderer<LivingEntity, EntityModel<LivingEntity>> renderer, boolean slim){
        this.slim = slim;
        this.model = new PlayerModel<>(0.0F, slim);
        this.hand = new ElectroHandRenderer(this);
        this.elytra = new ElectroElytraRenderer(this.model);
    }

    public void render(AbstractClientPlayerEntity player, float rotation, float partialTicks, MatrixStack stack, IRenderTypeBuffer buffers, int packedLight) {
        setModelProperties(player);
        stack.pushPose();
        this.model.attackTime = player.getAttackAnim(partialTicks);

        boolean shouldSit = player.isPassenger() && (player.getVehicle() != null && player.getVehicle().shouldRiderSit());
        this.model.riding = shouldSit;
        this.model.young = player.isBaby();
        float f = MathHelper.rotLerp(partialTicks, player.yBodyRotO, player.yBodyRot);
        float f1 = MathHelper.rotLerp(partialTicks, player.yHeadRotO, player.yHeadRot);
        float f2 = f1 - f;
        if (shouldSit && player.getVehicle() instanceof LivingEntity) {
            LivingEntity livingentity = (LivingEntity) player.getVehicle();
            f = MathHelper.rotLerp(partialTicks, livingentity.yBodyRotO, livingentity.yBodyRot);
            f2 = f1 - f;
            float f3 = MathHelper.wrapDegrees(f2);
            if (f3 < -85.0F) {
                f3 = -85.0F;
            }
            if (f3 >= 85.0F) {
                f3 = 85.0F;
            }
            f = f1 - f3;
            if (f3 * f3 > 2500.0F) {
                f += f3 * 0.2F;
            }
            f2 = f1 - f;
        }
        float f6 = MathHelper.lerp(partialTicks, player.xRotO, player.xRot);
        if (player.getPose() == Pose.SLEEPING) {
            Direction direction = player.getBedOrientation();
            if (direction != null) {
                float f4 = player.getEyeHeight(Pose.STANDING) - 0.1F;
                stack.translate((double) ((float) (-direction.getStepX()) * f4), 0.0D,
                        (double) ((float) (-direction.getStepZ()) * f4));
            }
        }
        float f7 = getBob(player, partialTicks);
        setupRotations(player, stack, f7, f, partialTicks);
        stack.scale(-1.0F, -1.0F, 1.0F);
        scale(stack, 0.95F);
        stack.translate(0.0D, (double) -1.501F, 0.0D);
        float f8 = 0.0F;
        float f5 = 0.0F;
        if (!shouldSit && player.isAlive()) {
            f8 = MathHelper.lerp(partialTicks, player.animationSpeedOld, player.animationSpeed);
            f5 = player.animationPosition - player.animationSpeed * (1.0F - partialTicks);
            if (player.isBaby()) {
                f5 *= 3.0F;
            }
            if (f8 > 1.0F) {
                f8 = 1.0F;
            }
        }
        this.model.prepareMobModel(player, f5, f8, partialTicks);
        this.model.setupAnim(player, f5, f8, f7, f2, f6);
        boolean visible = !player.isInvisible();
        boolean invisible = !visible && !player.isInvisibleTo(Minecraft.getInstance().player);
        RenderType rendertype = getRenderType(((float)player.tickCount + partialTicks) * 0.02F);
        if (rendertype != null) {
            IVertexBuilder ivertexbuilder = buffers.getBuffer(rendertype);
            this.model.renderToBuffer(stack, ivertexbuilder, packedLight, OverlayTexture.pack(OverlayTexture.u(0.0F), OverlayTexture.v(player.hurtTime > 0 || player.deathTime > 0)), 1.0F, 1.0F, 1.0F, invisible ? 0.15F : 1.0F);
        }
        if (!player.isSpectator()) {
            elytra.render(stack, buffers, packedLight, player, f5, f8, partialTicks, f7, f2, f6);
        }
        stack.popPose();
    }

    private static float getBob(AbstractClientPlayerEntity player, float partialTicks) {
        return (float)player.tickCount + partialTicks;
    }

    public static final ResourceLocation MISAKA_ELECTRO = new ResourceLocation(Nekoration.MODID, "textures/entity/misaka_mikoto.png");

    public static RenderType getRenderType(float frac) {
       return RenderType.energySwirl(MISAKA_ELECTRO, frac, frac);
    }

    private static void scale(MatrixStack stack, float frac) {
        stack.scale(frac, frac, frac);
    }

    private static void setupRotations(AbstractClientPlayerEntity player, MatrixStack stack, float viewBob, float lerp, float partialTicks) {
        float f = player.getSwimAmount(partialTicks);
        if (player.isFallFlying()) {
            setupBaseRotations(player, stack, viewBob, lerp, partialTicks);
            float f1 = (float) player.getFallFlyingTicks() + partialTicks;
            float f2 = MathHelper.clamp(f1 * f1 / 100.0F, 0.0F, 1.0F);
            if (!player.isAutoSpinAttack()) {
                stack.mulPose(Vector3f.XP.rotationDegrees(f2 * (-90.0F - player.xRot)));
            }

            Vector3d vector3d = player.getViewVector(partialTicks);
            Vector3d vector3d1 = player.getDeltaMovement();
            double d0 = Entity.getHorizontalDistanceSqr(vector3d1);
            double d1 = Entity.getHorizontalDistanceSqr(vector3d);
            if (d0 > 0.0D && d1 > 0.0D) {
                double d2 = (vector3d1.x * vector3d.x + vector3d1.z * vector3d.z) / Math.sqrt(d0 * d1);
                double d3 = vector3d1.x * vector3d.z - vector3d1.z * vector3d.x;
                stack.mulPose(Vector3f.YP.rotation((float) (Math.signum(d3) * Math.acos(d2))));
            }
        } else if (f > 0.0F) {
            setupBaseRotations(player, stack, viewBob, lerp, partialTicks);
            float f3 = player.isInWater() ? -90.0F - player.xRot : -90.0F;
            float f4 = MathHelper.lerp(f, 0.0F, f3);
            stack.mulPose(Vector3f.XP.rotationDegrees(f4));
            if (player.isVisuallySwimming()) {
                stack.translate(0.0D, -1.0D, (double) 0.3F);
            }
        } else {
            setupBaseRotations(player, stack, viewBob, lerp, partialTicks);
        }
    }

    private static void setupBaseRotations(AbstractClientPlayerEntity player, MatrixStack stack, float viewBob, float lerp, float partialTicks) {
        Pose pose = player.getPose();
        if (pose != Pose.SLEEPING) {
            stack.mulPose(Vector3f.YP.rotationDegrees(180.0F - lerp));
        }

        if (player.deathTime > 0) {
            float f = ((float) player.deathTime + partialTicks - 1.0F) / 20.0F * 1.6F;
            f = MathHelper.sqrt(f);
            if (f > 1.0F) {
                f = 1.0F;
            }

            stack.mulPose(Vector3f.ZP.rotationDegrees(f * 90.0F));
        } else if (player.isAutoSpinAttack()) {
            stack.mulPose(Vector3f.XP.rotationDegrees(-90.0F - player.xRot));
            stack.mulPose(Vector3f.YP.rotationDegrees(((float) player.tickCount + partialTicks) * -75.0F));
        } else if (pose == Pose.SLEEPING) {
            Direction direction = player.getBedOrientation();
            float f1 = direction != null ? sleepDirectionToRotation(direction) : lerp;
            stack.mulPose(Vector3f.YP.rotationDegrees(f1));
            stack.mulPose(Vector3f.ZP.rotationDegrees(90.0F));
            stack.mulPose(Vector3f.YP.rotationDegrees(270.0F));
        } else if (player.hasCustomName() || player instanceof PlayerEntity) {
            String s = TextFormatting.stripFormatting(player.getName().getString());
            if (("Dinnerbone".equals(s) || "Grumm".equals(s)) && (!(player instanceof PlayerEntity) || ((PlayerEntity) player).isModelPartShown(PlayerModelPart.CAPE))) {
                stack.translate(0.0D, (double) (player.getBbHeight() + 0.1F), 0.0D);
                stack.mulPose(Vector3f.ZP.rotationDegrees(180.0F));
            }
        }
    }

    private static float sleepDirectionToRotation(Direction direction) {
        switch(direction) {
        case SOUTH:
           return 90.0F;
        case WEST:
           return 0.0F;
        case NORTH:
           return 270.0F;
        case EAST:
           return 180.0F;
        default:
           return 0.0F;
        }
    }

    private void setModelProperties(AbstractClientPlayerEntity player) {
        if (player.isSpectator()) {
            this.model.setAllVisible(false);
            this.model.head.visible = true;
            this.model.hat.visible = true;
        } else {
            this.model.setAllVisible(true);
            this.model.hat.visible = player.isModelPartShown(PlayerModelPart.HAT);
            this.model.jacket.visible = player.isModelPartShown(PlayerModelPart.JACKET);
            this.model.leftPants.visible = player.isModelPartShown(PlayerModelPart.LEFT_PANTS_LEG);
            this.model.rightPants.visible = player.isModelPartShown(PlayerModelPart.RIGHT_PANTS_LEG);
            this.model.leftSleeve.visible = player.isModelPartShown(PlayerModelPart.LEFT_SLEEVE);
            this.model.rightSleeve.visible = player.isModelPartShown(PlayerModelPart.RIGHT_SLEEVE);
            this.model.crouching = player.isCrouching();
            BipedModel.ArmPose mainPose = getArmPose(player, Hand.MAIN_HAND);
            BipedModel.ArmPose offPose = getArmPose(player, Hand.OFF_HAND);
            if (mainPose.isTwoHanded()) {
                offPose = player.getOffhandItem().isEmpty() ? BipedModel.ArmPose.EMPTY : BipedModel.ArmPose.ITEM;
            }
            if (player.getMainArm() == HandSide.RIGHT) {
                this.model.rightArmPose = mainPose;
                this.model.leftArmPose = offPose;
            } else {
                this.model.rightArmPose = offPose;
                this.model.leftArmPose = mainPose;
            }
        }
    }

    public void renderRightHand(float partialTicks, MatrixStack stack, IRenderTypeBuffer buffers, int packedLight, AbstractClientPlayerEntity player) {
        renderHand(partialTicks, stack, buffers, packedLight, player, (this.model).rightArm, (this.model).rightSleeve);
    }

    public void renderLeftHand(float partialTicks, MatrixStack stack, IRenderTypeBuffer buffers, int packedLight, AbstractClientPlayerEntity player) {
        renderHand(partialTicks, stack, buffers, packedLight, player, (this.model).leftArm, (this.model).leftSleeve);
    }

    private void renderHand(float partialTicks, MatrixStack stack, IRenderTypeBuffer buffers, int packedLight, AbstractClientPlayerEntity player, ModelRenderer arm, ModelRenderer sleeves) {
        setModelProperties(player);
        this.model.attackTime = 0.0F;
        this.model.crouching = false;
        this.model.swimAmount = 0.0F;
        this.model.setupAnim(player, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F);
        arm.xRot = 0.0F;
        arm.render(stack, buffers.getBuffer(getRenderType(((float)player.tickCount + partialTicks) * 0.02F)), packedLight, OverlayTexture.NO_OVERLAY);
        sleeves.xRot = 0.0F;
        sleeves.render(stack, buffers.getBuffer(getRenderType(((float)player.tickCount + partialTicks) * 0.02F)), packedLight, OverlayTexture.NO_OVERLAY);
    }

    private static BipedModel.ArmPose getArmPose(AbstractClientPlayerEntity player, Hand hand) {
        ItemStack itemstack = player.getItemInHand(hand);
        if (itemstack.isEmpty()) {
            return BipedModel.ArmPose.EMPTY;
        } else {
            if (player.getUsedItemHand() == hand && player.getUseItemRemainingTicks() > 0) {
                UseAction useaction = itemstack.getUseAnimation();
                if (useaction == UseAction.BLOCK) {
                    return BipedModel.ArmPose.BLOCK;
                }
                if (useaction == UseAction.BOW) {
                    return BipedModel.ArmPose.BOW_AND_ARROW;
                }
                if (useaction == UseAction.SPEAR) {
                    return BipedModel.ArmPose.THROW_SPEAR;
                }
                if (useaction == UseAction.CROSSBOW && hand == player.getUsedItemHand()) {
                    return BipedModel.ArmPose.CROSSBOW_CHARGE;
                }
            } else if (!player.swinging && itemstack.getItem() == Items.CROSSBOW && CrossbowItem.isCharged(itemstack)) {
                return BipedModel.ArmPose.CROSSBOW_HOLD;
            }
            return BipedModel.ArmPose.ITEM;
        }
    }
}
